package com.jxzn.locust.config;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.beans.factory.annotation.AnnotatedBeanDefinition;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.EnvironmentAware;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.ScannedGenericBeanDefinition;
import org.springframework.core.env.Environment;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.core.type.classreading.MetadataReader;
import org.springframework.core.type.classreading.MetadataReaderFactory;
import org.springframework.core.type.classreading.SimpleMetadataReaderFactory;
import org.springframework.core.type.filter.AnnotationTypeFilter;
import org.springframework.util.ClassUtils;
import org.springframework.util.MultiValueMap;

import com.jxzn.locust.dto.ModuleAuthDTO;
import com.jxzn.locust.support.Auth;
import com.jxzn.locust.support.AuthScan;
import com.jxzn.locust.support.Module;

public class AuthScanRegister implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, BeanClassLoaderAware, EnvironmentAware {

    Logger logger = LoggerFactory.getLogger(getClass());

    ResourceLoader resourceLoader;
    ClassLoader classLoader;
    Environment environment;

    public static Map<Module, ModuleAuthDTO> moduleMap = new HashMap<>();

    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        this.classLoader = classLoader;
    }

    @Override
    public void setResourceLoader(ResourceLoader resourceLoader) {
        this.resourceLoader = resourceLoader;
    }

    @Override
    public void registerBeanDefinitions(AnnotationMetadata md, BeanDefinitionRegistry registry) {
        ClassPathScanningCandidateComponentProvider scanner = getScanner();
        scanner.setResourceLoader(this.resourceLoader);

        scanner.addIncludeFilter(new AnnotationTypeFilter(Auth.class));
        scanner.addIncludeFilter(new AnnotationTypeFilter(Module.class));

        Set<String> basePackages = getBasePackages(md);

        for (String pkg : basePackages) {
            Set<BeanDefinition> bdSet = new LinkedHashSet<>();

            ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            pkg = pkg.replace(".", "/");
            try {
                Resource[] resources = resolver.getResources("classpath*:" + pkg);
                MetadataReaderFactory mdrf = new SimpleMetadataReaderFactory();
                for (Resource res : resources) {
                    MetadataReader mdr = mdrf.getMetadataReader(res);
                    ScannedGenericBeanDefinition sgbd = new ScannedGenericBeanDefinition(mdr);
                    sgbd.setResource(res);
                    sgbd.setSource(res);
                    bdSet.add(sgbd);
                }

                for (BeanDefinition bd : bdSet) {
                    String className = bd.getBeanClassName();

                    Class<?> clazz = Class.forName(className);
                    Module module = clazz.getAnnotation(Module.class);
                    if (null == module) {
                        continue;
                    }
                    if (!moduleMap.containsKey(module)) {
                        moduleMap.put(module, new ModuleAuthDTO(module));
                    }

                    Method[] methods = clazz.getDeclaredMethods();
                    for (Method method : methods) {
                        Auth auth = method.getAnnotation(Auth.class);
                        if (null == auth) {
                            continue;
                        }
                        moduleMap.get(module).getAuthSet().add(auth);
                    }

                    Class<?> parent = clazz.getSuperclass();
                    if (parent.getName().contains("NamedEntityController")) {
                        methods = parent.getDeclaredMethods();
                        for (Method method : methods) {
                            Auth auth = method.getAnnotation(Auth.class);
                            if (null == auth) {
                                continue;
                            }
                            moduleMap.get(module).getAuthSet().add(auth);
                        }
                    }
                }

            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }

        }

    }

    ClassPathScanningCandidateComponentProvider getScanner() {
        return new ClassPathScanningCandidateComponentProvider(false, this.environment) {
            @Override
            protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
                if (!beanDefinition.getMetadata().isIndependent()) {
                    return false;
                }
                AnnotationMetadata md = beanDefinition.getMetadata();
                if (md.isInterface() && md.getInterfaceNames().length == 1 && Annotation.class.getName().equals(md.getInterfaceNames()[0])) {
                    Class<?> target;
                    try {
                        target = ClassUtils.forName(md.getClassName(), AuthScanRegister.this.classLoader);
                        return !target.isAnnotation();
                    } catch (Exception e) {
                        logger.error(e.getMessage(), e);
                    }
                }
                return false;
            }
        };
    }

    Set<String> getBasePackages(AnnotationMetadata metadata) {
        MultiValueMap<String, Object> attributes = metadata.getAllAnnotationAttributes(AuthScan.class.getCanonicalName());
        Set<String> basePackages = new HashSet<String>();
        for (Object obj : attributes.get("basePackages")) {
            if (null == obj) {
                continue;
            }
            String[] pkgList = (String[]) obj;
            for (Object j : pkgList) {
                if (null == j) {
                    continue;
                }
                String pkg = j.toString();
                if (StringUtils.isNoneBlank(pkg)) {
                    basePackages.add(pkg);
                    logger.info("package={}", pkg);
                }
            }
        }
        return basePackages;
    }
}
